{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "f8926c50",
   "metadata": {},
   "source": [
    "# 上下文管理器"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "afc84283",
   "metadata": {},
   "source": [
    "读写文件时，如果一个文件被打开，且未被正常关闭，可能会出现一些意想不到的结果。\n",
    "\n",
    "Python提供了上下文管理器的机制来解决这个问题，它通常与关键字with一起使用。对于上面的例子，用with语句调用的方式为：\n",
    "\n",
    "```python\n",
    "with <expression>:\n",
    "    <statements>\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "f502a6f5",
   "metadata": {},
   "outputs": [],
   "source": [
    "with open('my_file', 'w') as fp:\n",
    "    # do stuff with fp\n",
    "    data = fp.write(\"Hello world\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7d172aca",
   "metadata": {},
   "source": [
    "等价于："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "d0609f8b",
   "metadata": {},
   "outputs": [],
   "source": [
    "fp = open('my_file', 'w')\n",
    "try:\n",
    "    # do stuff with f\n",
    "    data = fp.write(\"Hello world\")\n",
    "finally:\n",
    "    fp.close()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d3641564",
   "metadata": {},
   "source": [
    "处理文件，线程，数据库，网络编程等等资源的时候，经常需要使用上面这样的代码形式，以确保资源的正常使用和释放。\n",
    "\n",
    "上下文管理器需要`<expression>`中的结果能够支持`.__enter__()`和`.__exit__()`方法："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "f248d38c",
   "metadata": {},
   "outputs": [],
   "source": [
    "fp = open('my_file', 'w')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "62c231ca",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<function TextIOWrapper.__enter__>"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fp.__enter__"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "8f11e40c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<function TextIOWrapper.__exit__>"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fp.__exit__"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9c4b4556",
   "metadata": {},
   "source": [
    "## 自定义上下文管理器\n",
    "\n",
    "可以定义一个支持上述方法的自定义上下文管理器："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "0623b88b",
   "metadata": {},
   "outputs": [],
   "source": [
    "class TestManager(object):\n",
    "    \n",
    "    def __enter__(self):\n",
    "        print(\"Entering\")\n",
    "    \n",
    "    def __exit__(self, exc_type, exc_value, traceback):\n",
    "        print(\"Exiting\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "99a42705",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Entering\n",
      "Hello\n",
      "Exiting\n"
     ]
    }
   ],
   "source": [
    "with TestManager():\n",
    "    print(\"Hello\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4972036c",
   "metadata": {},
   "source": [
    "如果<statements>在执行过程中抛出了异常，.__exit__()方法会先被执行，然后抛出异常："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "599f8673",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Entering\n",
      "Exiting\n"
     ]
    },
    {
     "ename": "ZeroDivisionError",
     "evalue": "division by zero",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mZeroDivisionError\u001b[0m                         Traceback (most recent call last)",
      "Input \u001b[0;32mIn [8]\u001b[0m, in \u001b[0;36m<cell line: 1>\u001b[0;34m()\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m TestManager():\n\u001b[0;32m----> 2\u001b[0m     \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;241;43m/\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;241;43m0\u001b[39;49m)\n",
      "\u001b[0;31mZeroDivisionError\u001b[0m: division by zero"
     ]
    }
   ],
   "source": [
    "with TestManager():\n",
    "    print(1 / 0)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fbc56f12",
   "metadata": {},
   "source": [
    "## 方法.__enter__()的返回值\n",
    "\n",
    "在读文件的例子中，在`<statements>`中使用文件对象时使用了as关键字的形式，将`open()`函数返回的文件对象赋给了f。事实上，as关键字只是将上下文管理器`.__enter__()`方法的返回值赋给了f，而文件对象的`.__enter__()`方法的返回值刚好是它本身："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "fd686320",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "fp = open('my_file', 'w')\n",
    "\n",
    "fp.__enter__() is fp"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "e8364f1a",
   "metadata": {},
   "outputs": [],
   "source": [
    "fp.close()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5c2978c3",
   "metadata": {},
   "source": [
    "一个通常的做法是将`.__enter__()`方法的返回值设为这个上下文管理器对象本身，也可以是其他值："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "21c53e92",
   "metadata": {},
   "outputs": [],
   "source": [
    "class TestManager(object):\n",
    "    \n",
    "    def __enter__(self):\n",
    "        print(\"Entering\")\n",
    "        return \"Hello\"\n",
    "    \n",
    "    def __exit__(self, exc_type, exc_value, traceback):\n",
    "        print(\"Exiting\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "a7e1fede",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Entering\n",
      "Hello\n",
      "Exiting\n"
     ]
    }
   ],
   "source": [
    "with TestManager() as f:\n",
    "    print(f)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "12d97d29",
   "metadata": {},
   "source": [
    "## 错误处理\n",
    "\n",
    "`__exit__()`方法接受的参数中有一些错误信息，如果没有错误，这些参数为`None`，如果有错误，可以在这个方法里对一些错误进行处理："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "3fe439cc",
   "metadata": {},
   "outputs": [],
   "source": [
    "class TestManager(object):\n",
    "    \n",
    "    def __enter__(self):\n",
    "        print(\"Entering\")\n",
    "    \n",
    "    def __exit__(self, exc_type, exc_value, traceback):\n",
    "        print(\"Exiting\")\n",
    "        if exc_type is not None:\n",
    "            print(f\"Exception: {exc_value}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "a1173507",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Entering\n",
      "Exiting\n",
      "Exception: division by zero\n"
     ]
    },
    {
     "ename": "ZeroDivisionError",
     "evalue": "division by zero",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mZeroDivisionError\u001b[0m                         Traceback (most recent call last)",
      "Input \u001b[0;32mIn [14]\u001b[0m, in \u001b[0;36m<cell line: 1>\u001b[0;34m()\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m TestManager() \u001b[38;5;28;01mas\u001b[39;00m f:\n\u001b[0;32m----> 2\u001b[0m     \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;241;43m/\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;241;43m0\u001b[39;49m)\n",
      "\u001b[0;31mZeroDivisionError\u001b[0m: division by zero"
     ]
    }
   ],
   "source": [
    "with TestManager() as f:\n",
    "    print(1 / 0)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d4a28957",
   "metadata": {},
   "source": [
    "如果不想让异常继续抛出，只需要将`.__exit__()`方法的返回值设为`True`："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "9a03474b",
   "metadata": {},
   "outputs": [],
   "source": [
    "class TestManager(object):\n",
    "    \n",
    "    def __enter__(self):\n",
    "        print(\"Entering\")\n",
    "    \n",
    "    def __exit__(self, exc_type, exc_value, traceback):\n",
    "        print(\"Exiting\")\n",
    "        if exc_type is not None:\n",
    "            print(f\"Exception: {exc_value}\")\n",
    "        return True"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "fa73abc4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Entering\n",
      "Exiting\n",
      "Exception: division by zero\n"
     ]
    }
   ],
   "source": [
    "with TestManager() as f:\n",
    "    print(1 / 0)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cd5089cc",
   "metadata": {},
   "source": [
    "清理临时文件："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "f3e36b49",
   "metadata": {},
   "outputs": [],
   "source": [
    "%rm my_file"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7f76ef87",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
